package com.gf.framework.rabbitmq.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.gf.framework.common.utils.SpringContextUtil;
import com.gf.framework.rabbitmq.model.ExchangeInfo;
import com.gf.framework.rabbitmq.model.QueueInfo;
import com.gf.framework.rabbitmq.model.RabbitMessageType;
import com.gf.framework.rabbitmq.porperties.*;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.*;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author gaofei
 * @date 2022/5/26 19:24
 */
@Component
@Configuration
@Slf4j
public class RabbitMqConfig implements SmartInitializingSingleton {
    @Resource
    RabbitMqProperties rabbitMqProperties;

    @Bean
    public MessageConverter messageConverter() {
        if (rabbitMqProperties.getMessageType() == RabbitMessageType.JSON) {
            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
            JavaTimeModule timeModule = new JavaTimeModule();
            timeModule.addDeserializer(LocalDate.class,
                    new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
            timeModule.addDeserializer(LocalDateTime.class,
                    new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
            timeModule.addSerializer(LocalDate.class,
                    new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
            timeModule.addSerializer(LocalDateTime.class,
                    new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
            objectMapper.registerModule(timeModule);
            objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
            return new Jackson2JsonMessageConverter(objectMapper);
        }
        return new SerializerMessageConverter();
    }

    @SneakyThrows
    @Override
    public void afterSingletonsInstantiated() {
        Map<String, MqProperties> mqPropertiesMap = SpringContextUtil.getContext().getBeansOfType(MqProperties.class);

        for (MqProperties mqProperties : mqPropertiesMap.values()) {
            List<ExchangeInfo> exchangeInfoList = mqProperties.getExchanges();
            if (exchangeInfoList != null && !exchangeInfoList.isEmpty()) {
                initExchanges(exchangeInfoList);
            }
            List<QueueInfo> queueInfos = mqProperties.getQueues();
            if (queueInfos != null && !queueInfos.isEmpty()) {
                initQueue(queueInfos);
            }
        }
    }

    private void initExchanges(List<ExchangeInfo> exchangeInfos) throws Throwable {
        RabbitAdmin rabbitAdmin = new RabbitAdmin(SpringContextUtil.getBean(RabbitTemplate.class));
        for (ExchangeInfo exchangeInfo: exchangeInfos) {
            Map<String, Object> arguments = new HashMap<>();
            if (exchangeInfo.getAlternateExchange() != null && !exchangeInfo.getAlternateExchange().isEmpty()) {
                arguments.put("alternate-exchange", exchangeInfo.getAlternateExchange());
            }
            Exchange exchange;

            switch (exchangeInfo.getType()) {
                case TOPIC:
                    exchange = new TopicExchange(exchangeInfo.getName(), exchangeInfo.getDurable(), exchangeInfo.getAutoDelete(), arguments);
                    break;
                case DIRECT:
                    exchange = new DirectExchange(exchangeInfo.getName(), exchangeInfo.getDurable(), exchangeInfo.getAutoDelete(), arguments);
                    break;
                case FANOUT:
                    exchange = new FanoutExchange(exchangeInfo.getName(), exchangeInfo.getDurable(), exchangeInfo.getAutoDelete(), arguments);
                    break;
                case HEADERS:
                    exchange = new HeadersExchange(exchangeInfo.getName(), exchangeInfo.getDurable(), exchangeInfo.getDurable(), arguments);
                    break;
                default:
                    throw new Throwable("exchangeType error");
            }
            rabbitAdmin.declareExchange(exchange);
            log.info("declareExchange:" + exchange);
        }
    }

    private void initQueue(List<QueueInfo> queueInfos) {
        RabbitAdmin rabbitAdmin = new RabbitAdmin(SpringContextUtil.getBean(RabbitTemplate.class));
        for (QueueInfo queueInfo : queueInfos) {
            Queue queue = new Queue(queueInfo.getName(), queueInfo.isDurable(), queueInfo.isExclusive(),
                    queueInfo.isAutoDelete(),
                    queueInfo.getArguments());
            rabbitAdmin.declareQueue(queue);
            log.info("declareQueue:" + queue);
            if (queueInfo.getBindingInfos() != null) {
                queueInfo.getBindingInfos().forEach(bindingInfo -> {
                    Binding binding = new Binding(queue.getName(), Binding.DestinationType.QUEUE, bindingInfo.getFromExchange(),
                            bindingInfo.getRoutingKey(), null);
                    rabbitAdmin.declareBinding(binding);
                    log.info(String.format("declareBinding:%s", binding));
                });
            }
        }
    }
}
